From Bouncing Ball to Angry Birds

This notebook explores the idea that objects on our sketch appear to move in various ways:

  • Falls
  • Projectiles
  • Bounces

This is a magic trick... nothing really "moves"... it is just the canvas redrawn in an appropriate way that gives the illusion of movement.

Stationary Object

We will use our drawObject code from last week. First, let's create a sketch where an object is drawn in position (x,y), where x and y are global variables. This allows setup() to initialize them, and draw() to use them.

In [5]:
// Global variables: 
// defined here, used in setup() and draw()

int x;
int y;

void setup() {
    size(200, 200);
    x = width/2;
    y = 50;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, w, h);
}

void draw() {
    drawBall(x, y, 20, 10);
}
Sketch #5:

Sketch #5 state: Loading...

Nothing too interesting there. I did use the special variables width and height that are automatically defined to be the size of the canvas. But other than that, it is just a picture of a red ball sitting quietly in space.

Brownian Motion

Let's try a slight variation. Do you know the idea of Brownian Motion (sometimes called particle theory). This is basically just the idea that small objects will randomly move because they are hit will atoms or molecules.

Warning: I did not say "Brownie in motion"... that is a small chocolate treat that is on the move.

We can simulate Brownian Motion by simply moving the ball a little left, right, or stay in the same place. We use the fact that random(2) - 1 represents either a -1, 0, or 1.

In [57]:
// Global variables: 
// defined here, used in setup() and draw()

int x;
int y;

void setup() {
    size(200, 200);
    x = width/2;
    y = width/2;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    drawBall(x, y, 10, 10);
    x += random(2) - 1;
    y += random(2) - 1;
}
Sketch #57:

Sketch #57 state: Loading...

It looks like the ball is moving randomly about, doesn't it. You can make the effect even stronger by clearing the background before you redraw the ball:

In [58]:
// Global variables: 
// defined here, used in setup() and draw()

int x;
int y;

void setup() {
    size(200, 200);
    x = width/2;
    y = width/2;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    background();
    drawBall(x, y, 10, 10);
    x += random(2) - 1;
    y += random(2) - 1;
}
Sketch #58:

Sketch #58 state: Loading...

Velocity

Now, instead of having the ball randomly move each time step, let's have the ball move in a particular direct. To do this, we will create two new variables that will keep track of the velocity (speed) of the ball in the x and y directions.

On each time step, we will move the ball an appropriate amount.

In [59]:
// Give the ball velocities 
// in the x and y directions:

float vx;
float vy;

int x;
int y;

float dt;  
float t;

void setup() {
    size(200, 200);
    x = width/2;
    y = 50;
    dt = 0.1;  
    t = 0;
    vx = 50.0;
    vy = 50.0;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    dx = vx * dt;    
    x = x + dx;
    
    dy = vy * dt;
    y = y + dy;

    drawBall(x, y, 10, 10);
    
    t = t + dt;
}
Sketch #59:

Sketch #59 state: Loading...

Somewhat interesting... but where did it go?

Bouncing off the walls

To keep the ball "on the canvas", we need to see when it is beyond bounds, and turn the ball around. All we need to do is switch the velocity to go in the opposite direction. So if the ball is going to the right and hits a wall, we make it go to the left.

In [6]:
float vx;
float vy;

int x;
int y;

float dt;  
float t;

void setup() {
    size(200, 200);
    x = width/2;
    y = 50;
    dt = 0.1;  
    t = 0;
    vx = 50.0;
    vy = 40.0;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    dx = vx * dt;    
    if (((x + dx) > width) || ((x + dx) < 0)) {
        vx = vx * -1;
    } else {
        x = x + dx;
    }
    
    dy = vy * dt;
    if (((y + dy) > height) || ((y + dy) < 0)) {
        vy = vy * -1;
    } else {
        y = y + dy;
    }

    drawBall(x, y, 10, 10);
    
    t = t + dt;
}
Sketch #6:

Sketch #6 state: Loading...

Gravity

Now, the final step: let's add gravity. At each timestep, we'll add a gravity component to vy. Over time, that will get bigger and bigger.

In [1]:
float g = 9.8; 

float vx;
float vy;

int x;
int y;

float dt;  
float t;

void setup() {
    size(200, 500);
    x = width/2;
    y = 50;
    dt = 0.1;  
    t = 0;
    vx = 50.0;
    vy = 50.0;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    // gravity
    vy = vy + g * dt;
    
    dx = vx * dt;    
    if (((x + dx) > width) || ((x + dx) < 0)) {
        vx = vx * -1;
    } else {
        x = x + dx;
    }
    
    dy = vy * dt;
    if (((y + dy) > height) || ((y + dy) < 0)) {
        vy = vy * -1;
    } else {
        y = y + dy;
    }

    drawBall(x, y, 10, 10);
    
    t = t + dt;
}
Sketch #1:

Sketch #1 state: Loading...

Dampening

That is weird... it keeps bouncing! We probably want to lose a little bit of energy each time it "hits something." We merely don't give the full amount when we change directions.

In [62]:
float g = 9.8; 

float vx;
float vy;

int x;
int y;

float dt;  
float t;

void setup() {
    size(200, 500);
    x = width/2;
    y = 50;
    dt = 0.1;  
    t = 0;
    vx = 50.0;
    vy = 0.0;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    // gravity
    vy = vy + g * dt;
    
    dx = vx * dt;    
    if (((x + dx) > width) || ((x + dx) < 0)) {
        vx = vx * -0.8;
    } else {
        x = x + dx;
    }
    
    dy = vy * dt;
    if (((y + dy) > height) || ((y + dy) < 0)) {
        vy = vy * -0.8;
    } else {
        y = y + dy;
    }

    drawBall(x, y, 10, 10);
    
    t = t + dt;
}
Sketch #62:

Sketch #62 state: Loading...

Interactivity

Nice! Now let's drop the ball with the mouse:

In [7]:
float g = 9.8; 

float vx;
float vy;

int x;
int y;

float dt;  
float t;

void setup() {
    size(200, 500);
    x = width/2;
    y = 50;
    dt = 0.1;  
    t = 0;
    vx = 50.0;
    vy = 0.0;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, 10, 10);
}

void draw() {
    // gravity
    background();
    vy = vy + g * dt;
    
    dx = vx * dt;    
    if (((x + dx) > width) || ((x + dx) < 0)) {
        vx = vx * -0.8;
    } else {
        x = x + dx;
    }
    
    dy = vy * dt;
    if (((y + dy) > height) || ((y + dy) < 0)) {
        vy = vy * -0.8;
    } else {
        y = y + dy;
    }

    drawBall(x, y, 10, 10);
    
    t = t + dt;
}

void mousePressed() {
    x = mouseX;
    y = mouseY;
    vx = 0.0;
    vy = 0.0;
}
Sketch #7:

Sketch #7 state: Loading...

Angry Birds

Ok, but it just drops. What if we could click, drag, and release, and the distance we dragged was proportional to a vector in the opposite direction of where we want to send it. Angry Birds!

Click below and drag:

In [19]:
float g = 9.8; 

float vx;
float vy;

int x;
int y;

float dt;  
float t;

void setup() {
    size(500, 500);
    x = width/2;
    y = 50;
    dt = 0.2;  
    t = 0;
    vx = 0.0;
    vy = 0.0;
}

void drawBall(int x, int y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, w, h);
}

boolean mouseIsDown = true;

void draw() {
    if (mouseIsDown) return;
    background();
    
    // gravity
    vy = vy + g * dt;
    
    dx = vx * dt;    
    if (((x + dx) > width) || ((x + dx) < 0)) {
        vx = vx * -0.8;
    } else {
        x = x + dx;
    }
    
    dy = vy * dt;
    if (((y + dy) > height) || ((y + dy) < 0)) {
        vy = vy * -0.8;
        drawBall(x, y, vy, 5);
        noLoop();
        return;
    } else {
        y = y + dy;
    }

    drawBall(x, y, 10, 10);
    
    t = t + dt;
}

void mousePressed() {
    mouseIsDown = true;
    x = mouseX;
    y = mouseY;
    background();
    drawBall(x, y, 10, 10);
    vx = 0.0;
    vy = 0.0;
}

void mouseReleased() {
    vx = (x - mouseX);
    vy = (y - mouseY);
    mouseIsDown = false;
    loop();
}

void mouseDragged() {
    background();
    drawBall(x, y, 10, 10);
    line(x, y, mouseX, mouseY);
}
Sketch #19:

Sketch #19 state: Loading...

To make a really "angry birds" like game, we'll need to detect more than collisions with the walls, floor, and ceiling. We'll do that later, once we learn how to make real objects.

Conclusion

In this notebook we have seen that motion is just imagined. We saw Brownian Motion, velocity, gravity, and a ball with an applied force (ala Angry Birds).